/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.factory.server.scheduler;

import com.factory.server.bo.Edge;
import com.factory.server.bo.Prime;
import com.factory.server.bo.UserInput;
import com.factory.server.bo.UserTask;
import com.factory.server.cache.InputCache;
import com.factory.server.consumers.SchedulerTasksQueue;
import com.factory.server.dao.DAODispatcherIface;
import com.factory.server.dto.EdgeDTO;
import com.factory.server.dto.UserTaskDTO;
import com.factory.server.helper.InputWrapper;
import com.factory.server.json.Plan;
import com.factory.server.json.SchedulerResult;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * SPT problem time plans computation.
 * @author milos
 */
@Service
public class SchedulerSPT {

    static final Logger LOG = Logger.getLogger(SchedulerSpanningTree.class.getName());
    @Autowired
    @Qualifier("daoDispatcher")
    private DAODispatcherIface genericDAODispacher;
    private SchedulerTasksQueue schedulerTasksQueue;
    private int numberOfThreads;

    public SchedulerResult scheduleSPT(UserTaskDTO userTaskDTO, SchedulerTasksQueue schedulerTasksQueue, int numberOfThreads) {
        this.schedulerTasksQueue = schedulerTasksQueue;
        this.numberOfThreads = numberOfThreads;
        Set<Integer> nodes = new HashSet<Integer>();
        List<Edge> edgeList;
        InputWrapper inputFromCache = InputCache.get(userTaskDTO.getInputId());
        if (inputFromCache != null) {
            edgeList = (List<Edge>) (List<?>) inputFromCache.getDBRecords();
        } else {
            edgeList = genericDAODispacher.getByProperty("inputId", userTaskDTO.getInputId(), Edge.class);
        }
        UserTask userTask = genericDAODispacher.getByProperty("inputId", userTaskDTO.getInputId(), UserTask.class).get(0);
        List<EdgeDTO> edgeDTOList = new ArrayList<EdgeDTO>(edgeList.size());
        boolean betweenAllNodes = true;
        for (Edge edge : edgeList) {
            if (edge.getSource() == null) {
                betweenAllNodes = false;
            }
            nodes.add(edge.getSource());
            nodes.add(edge.getTarget());
            edgeDTOList.add(new EdgeDTO(edge.getSource(), edge.getTarget(), edge.getCost()));
        }


        if (betweenAllNodes) {
            //SPT to all nodes
            userTask.setNote("FLOYD-WARSHALL");
            genericDAODispacher.saveOrUpdate(userTask);
            return schedulerFloydWarshall(userTaskDTO, nodes.size(), edgeDTOList.size());

        } else {
            System.out.println("Bellman-Ford will be used for path between 2 nodes.");
            userTask.setNote("BELLMAN-FORD");
            genericDAODispacher.saveOrUpdate(userTask);
            return schedulerBellmanFord(userTaskDTO, nodes.size(), edgeDTOList.size());
        }
    }

    private SchedulerResult schedulerBellmanFord(UserTaskDTO userTaskDTO, int nodeCount, int edgeCount) {
        LOG.log(Level.INFO, "Scheduling for Bellman-Ford started. Input id: " + userTaskDTO.getInputId());
        long computationTime = (long) ((long) nodeCount * (edgeCount + nodeCount) * 0.000024); //constant is 0.000024
        return createResult(computationTime, userTaskDTO);
    }

    private SchedulerResult schedulerFloydWarshall(UserTaskDTO userTaskDTO, int nodeCount, int edgesCount) {
        LOG.log(Level.INFO, "Scheduling for Floyd-Warshall started. Input id: " + userTaskDTO.getInputId());
        long computationTime = (long) (0.00000000043 * (Math.pow(nodeCount, 3) * edgesCount));
        return createResult(computationTime, userTaskDTO);
    }

    private String getStringTime(long millisTime) {

        String timeAsDateForPriorityPlan = String.format("%d hours, %d min, %d sec",
                TimeUnit.MILLISECONDS.toHours(millisTime),
                TimeUnit.MILLISECONDS.toMinutes(millisTime)
                - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisTime)),
                TimeUnit.MILLISECONDS.toSeconds(millisTime)
                - TimeUnit.HOURS.toSeconds(TimeUnit.MILLISECONDS.toHours(millisTime))
                - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisTime)
                - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisTime))));

        return timeAsDateForPriorityPlan;

    }

    private long getOffsetPriorityPlan(long computationTime) {
        MachineList machineList = new MachineList(numberOfThreads);
//        LOG.log(Level.INFO, "Size of schedulerTasksQueue: " + schedulerTasksQueue.getPlans().size());
        Plan priorityPlan = new Plan();
        priorityPlan.setComputationTime(computationTime);
        priorityPlan.setDeadline(0);
        int offsetForPriorityPlan = machineList.getOffset(schedulerTasksQueue, priorityPlan);
        return offsetForPriorityPlan;
    }

    private long getOffset(long computationTime) {
        MachineList machineList = new MachineList(numberOfThreads);
        long in12Hours = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12);
        Plan freePlan = new Plan();
        freePlan.setComputationTime(computationTime);
        freePlan.setDeadline(in12Hours);
        int offset = machineList.getOffset(schedulerTasksQueue, freePlan);
        return offset;
    }

    private SchedulerResult createResult(long computationTime, UserTaskDTO userTaskDTO) {
        ArrayList<Plan> plans = new ArrayList<Plan>();
        //going to compute offsets
        long offsetForPriorityPlan = getOffsetPriorityPlan(computationTime);
        long offset = getOffset(computationTime);

        long plannedFinishTime = computationTime + offset;
        long plannedPriorityFinishTime = (computationTime) + (offsetForPriorityPlan);

        String noPriorityTime = null;
        //kontrola ci nepresvihne deadline 1
        if (plannedFinishTime > TimeUnit.MINUTES.toMillis(1)) {
            noPriorityTime = "not guaranteed in 12 hours";
        } else {
            noPriorityTime = "in 12 hours";
        }

        String timeAsDateForPriorityPlan = getStringTime(plannedPriorityFinishTime);

        Plan noPriorityPlan = new Plan(userTaskDTO.getUserId(), userTaskDTO.getInputId(), userTaskDTO.getProblemId(), 4, noPriorityTime, plannedFinishTime, computationTime);
        noPriorityPlan.setDeadline(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(12)); //in12 hours
        plans.add(noPriorityPlan);

        plans.add(new Plan(userTaskDTO.getUserId(), userTaskDTO.getInputId(), userTaskDTO.getProblemId(), 8, "in " + timeAsDateForPriorityPlan, plannedPriorityFinishTime, computationTime));

        //cloud plan
        long plannedCloudFinishTime = computationTime;
        String timeAsDateForCloudPlan = getStringTime(computationTime);
        Plan cloudPlan = new Plan(userTaskDTO.getUserId(), userTaskDTO.getInputId(), userTaskDTO.getProblemId(), 12, timeAsDateForCloudPlan, plannedCloudFinishTime, computationTime);
        cloudPlan.setSupported(false);
        plans.add(cloudPlan);

        SchedulerResult schedulerResult = new SchedulerResult(userTaskDTO.getUserId(), userTaskDTO.getProblemId(), plans);
        return schedulerResult;

    }
}
